/*
 * Yabumi, a 2d scrolling shoot-em-up game.
 * Copyright (C) 2003 Gemma Teruaki <terugen@yabumi.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
 */

#include <stdlib.h>
#include <string.h>
#include <SDL.h>
#include <SDL_timer.h>
#include <math.h>
#include "util.h"

/* Following code (C) 2002 Jared Minch, released under GNU GPL version 2 
 * (see LICENSE for a copy), thanks aardvark! */
#define itofix(x) ((x) << 16)
#define ftofix(x) ((long)((x) * 65536.0 + ((x) < 0 ? -0.5 : 0.5)))
#define fixtoi(x) (((x) >> 16) + (((x) & 0x8000) >> 15))
#define fixtof(x) ((float) (x) / 65536.0)
#define fixdiv(x,y) ftofix(fixtof(x) / fixtof(y))

typedef long fixed;
/* End of code, thanks aardvark! */

_Key_Register Key_Register;

void * pmalloc(size_t BlockSize)
{
  void *p;
  p = malloc(BlockSize);
  if (p == NULL) {
    printf("Failed malloc \n");
    exit(1);
  };
  return p;
}

void pfree(void *BlockAddr)
{
  free(BlockAddr);
}

void Create_list(_list_header *header)
{
  _list_item *dummy1, *dummy2;
  dummy1 = (_list_item *)pmalloc(sizeof(_list_item));
  dummy2 = (_list_item *)pmalloc(sizeof(_list_item));
  /*        |->-header->-|    */
  /*        |            |    */
  /*     dummy2----<---dummy1 */

  dummy1->prev = NULL;
  dummy1->next = dummy2;
  dummy2->prev = dummy1;
  dummy2->next = NULL;
  header->prev = dummy2;
  header->next = dummy1;
}

void Free_list(_list_header *header)
{
  /*        N--->---header--->---|    */
  /*        |                    |    */
  /*     dummy2-<-2-<-1-<-0-<--dummy1 */

  _list_item *list_item, *temp;
  list_item = header->next;
  do {
    temp = list_item->next;
    pfree(list_item);
    list_item = temp;
  } while (list_item != NULL);
}

void Add_item_last(_list_header *header, _list_item *item)
{
  /*     |-->----header---->---|     */
  /*     |                     |     */
  /* dummy2-<-item2-<-item1-<---     */

  /*  dummy2-<-item-<-item2-<---     */
  item->next = header->prev;
  item->prev = header->prev->prev;
  header->prev->prev->next = item;
  header->prev->prev = item;
}

void Kill_item(_list_header *header, _list_item *item)
{
  item->prev->next = item->next;
  item->next->prev = item->prev;
}

void LoopWait()
{
  static Uint32 lasttime = 0;
  Uint32 nowtime, difftime;
  nowtime = SDL_GetTicks();
  difftime = nowtime - lasttime;
  if (difftime < INTERVAL) { 
    SDL_Delay(INTERVAL - difftime);
  };
  lasttime = SDL_GetTicks();
}

void Read_Event()
{
  SDL_Event event;
  char *key;
  char *c;
  while ( SDL_PollEvent(&event) ) {
    key = SDL_GetKeyName(event.key.keysym.sym);
    switch (event.type) {
    case SDL_QUIT:
      exit(0);
      break;
    case SDL_KEYDOWN:
      c = "space";
      if ( strcmp(key, c) == 0 ) Key_Register.space = true;
      c = "up";
      if ( strcmp(key, c) == 0 ) Key_Register.up = true;
      c = "down";
      if ( strcmp(key, c) == 0 ) Key_Register.down = true;
      c = "left";
      if ( strcmp(key, c) == 0 ) Key_Register.left = true;
      c = "right";
      if ( strcmp(key, c) == 0 ) Key_Register.right = true;
      break;
    case SDL_KEYUP:
      key = SDL_GetKeyName(event.key.keysym.sym);
      c = "space";
      if ( strcmp(key, c) == 0 ) Key_Register.space = false;
      c = "up";
      if ( strcmp(key, c) == 0 ) Key_Register.up = false;
      c = "down";
      if ( strcmp(key, c) == 0 ) Key_Register.down = false;
      c = "left";
      if ( strcmp(key, c) == 0 ) Key_Register.left = false;
      c = "right";
      if ( strcmp(key, c) == 0 ) Key_Register.right = false;
      /* Special Key */
      c = "q";
      if ( strcmp(key, c) == 0 ) Key_Register.q = true;
      c = "z";
      if ( strcmp(key, c) == 0 ) Key_Register.z = true;
      c = "x";
      if ( strcmp(key, c) == 0 ) Key_Register.x = true;
      c = "c";
      if ( strcmp(key, c) == 0 ) Key_Register.c = true;
      c = "p";
      if ( strcmp(key, c) == 0 ) Key_Register.p = true;
      /* Special Key */
      break;
    };
  };
}

void cap_angle(int *angle)
{
  if (*angle >= 360) *angle -= 360;
  if (*angle < 0) *angle += 360;
}

int distance(int x1, int y1, int x2, int y2)
{
  int xdiff = x2 - x1;
  int ydiff = y2 - y1;
  int dist;
  dist = sqrt(xdiff * xdiff + ydiff * ydiff);
  return dist;
}

double atan3(int x, int y)
{
  double tangle;
  if (x == 0) {
    if (y >= 0) return (M_PI * 0.5);
    if (y < 0) return (M_PI * 1.5);
  };
  if (y == 0) {
    if (x >= 0) return 0;
    if (x < 0) return (M_PI);
  };

  tangle = atan2(x,y);
  
  if ((x > 0) & ( y > 0)) return (M_PI * 0.5) - tangle;
  if ((x < 0) & ( y > 0)) return (M_PI * 0.5) - tangle;
  if ((x < 0) & ( y < 0)) return (M_PI * 0.5) - tangle;
  if ((x > 0) & ( y < 0)) return (M_PI * 2.5) - tangle;
  return 0;
};

int ifclockwise(int s_angle,int t_angle)
{
  int a;
  a = t_angle - s_angle;
  if ((a >= -360) & ( a < -180)) return 0;
  if ((a >= -180) & ( a < 0)) return 1;
  if ((a >= 0) & ( a < 180)) return 0;
  if ((a >= 180) & ( a < 360)) return 1;
  return 0;
}

void int_wrap(int *value)
{
  if (*value >= 32766) *value = 1;
  if (*value <= -32766) *value = -1;
};

SDL_Surface* crop(SDL_Surface *s, SDL_Rect *src)
{
  SDL_Surface *image;
  image = SDL_CreateRGBSurface(SDL_SWSURFACE,src->w,src->h,16,0,0,0,0);
  if(image == NULL) {
    fprintf(stderr, "failed CreateRGBSurface : %s\n", SDL_GetError());
    exit(1);
  };
  SDL_BlitSurface(s, src, image, NULL);
  Uint32 flags = SDL_SRCCOLORKEY | SDL_RLEACCEL;
  Uint32 key = SDL_MapRGB(image->format,0,0,0);
  SDL_SetColorKey(image, flags, key);
  image = SDL_DisplayFormat(image);
  if ( image == NULL ) {
    fprintf(stderr, "Couldn't DisplayFormat : %s\n", SDL_GetError());
    exit(1);
  };
  return image;
}

/* Following function (C) 2002 Jared Minch, released under GNU GPL version 2 (see LICENSE for a copy), thanks aardvark! */
/* Rotates 8-, 16-, and 32-bit sprites.  24-bit sprites aren't handled; you
 * must convert to one of these color depths before calling this function.
 * */
SDL_Surface *rotate( SDL_Surface *s, float ang )
{
  /* Quadrant */
  short q;
  float offset_ang;
  
  /* Destination surface */
  SDL_Surface *dst;
  /* Destination surface size */
  short dstw, dsth;
  fixed dstwf, dsthf;
  fixed srcwf, srchf;
  
  /* Precalculated cos and sin */
  float cosang, sinang;
  
  /* Corner points */
  short corner_top, corner_left, corner_right, corner_bottom;
  
  /* source step values for each side (How far to move along the source side
   * for each vertical pixel) */
  fixed src_step_top, src_step_side;
  
  /* Source step values per horizontal pixel */
  fixed src_step_x, src_step_y;
  
  /* destination step values (how far to move horizontally for each vertical
   * pixel) */
  fixed dst_step_0, dst_step_1;
  
  /* Fixed and integer location source pixel locations */
  fixed src_left;
  fixed xf, yf;
  int xi, yi, xnew, ynew, dx, dy;
  
  /* Pointer step values */
  int psx, psy;
  
  /* Destination line and remaining line length */
  fixed dstl_left, dstl_right;
  int dsty, dstx;
  
  /* Maximum line length (Calculated to prevent accumulated error to cause
   * an overrun) */
  int maxx;
  
  /* Source and destination pointers */
  char *sp;
  char *dp, *dlp, *dfp;
  
  /* Current side segments: = 0 for first segment, = 1 for second */
  short seg_left, seg_right;
  
  char bpp;
  
  bpp = s->format->BytesPerPixel;
  /* convert to radians, line added by Chris Thielen 
   * under author's permission */
  ang = ((ang * M_PI) / 180.0);
  
  cosang = cosf(ang);
  sinang = sinf(ang);
  
  /* Determine the quadrant (0 - 3) */
  if(sinang >= 0) {
    q = (cosang >= 0) ? 0 : 1;
  } else {
    q = (cosang >= 0) ? 3 : 2;
  }
  
  offset_ang = ang - (M_PI_2 * (float)q);
  
  /* Determine the size of the rotated surface, and the corner points */
  if(q == 0 || q == 2) {
    corner_top = (s->w - 1) * cosf(offset_ang);
    corner_left = (s->w - 1) * sinf(offset_ang);
    corner_right = (s->h - 1) * cosf(offset_ang);
    corner_bottom = (s->h - 1) * sinf(offset_ang);
  } else {
    corner_top = (s->h - 1) * cosf(offset_ang);
    corner_left = (s->h - 1) * sinf(offset_ang);
    corner_right = (s->w - 1) * cosf(offset_ang);
    corner_bottom = (s->w - 1) * sinf(offset_ang);
  }
  
  /* Width = w cos * h sin; Height = h cos * w sin */
  dstw = corner_top + corner_bottom + 1;
  dsth = corner_left + corner_right + 1;
  dstwf = itofix(dstw - 1);
  dsthf = itofix(dsth - 1);
  
  srcwf = itofix(s->w - 1);
  srchf = itofix(s->h - 1);
  
  /* Create the destination surface */
  dst = SDL_CreateRGBSurface(SDL_SWSURFACE, dstw, dsth,\
			     s->format->BitsPerPixel,\
			     s->format->Rmask, s->format->Gmask,\
			     s->format->Bmask, s->format->Amask);
  SDL_SetColorKey(dst, SDL_SRCCOLORKEY | SDL_RLEACCEL,\
		  (Uint32)SDL_MapRGB(dst->format, 0, 0, 0));
  
//aotf_assert(ERR_FATAL, dst,
//"Allocation of new zoomed surface failed.");
  
  /* Calculate step values */
  src_step_x = ftofix(cosang);
  src_step_y = ftofix(sinang);
  src_step_top = -ftofix(1.0 / sinang);
  src_step_side = ftofix(1.0 / cosang);
  dst_step_0 = ftofix(1.0 / tanf(offset_ang));
  dst_step_1 = ftofix(tanf(offset_ang));
  
  psx = bpp;
  psy = s->pitch;
  
  /* All right; do the actual rotation copy */
  SDL_LockSurface(dst);
  SDL_LockSurface(s);
  seg_left = seg_right = 0;
  dlp = dst->pixels;
  dstl_left = dstl_right = itofix(corner_top);
  
  switch(q) {
  case 0:
    src_left = srcwf;
    break;
  case 1:
    src_left = srchf;
    break;
  case 2:
    src_left = 0;
    break;
  case 3:
    src_left = 0;
    break;
  }
  
  for(dsty = 0; dsty < dsth; dsty++, dlp += dst->pitch) {
    
    /* If at a corner point, reset pointers and update seg_foo */
    if(dsty == corner_left) {
      dstl_left = 0;
      seg_left = 1;
      switch(q) {
      case 0:
	src_left = 0;
	break;
      case 1:
	src_left = srcwf;
	break;
      case 2:
	src_left = srchf;
	break;
      case 3:
	src_left = 0;
	break;
      }
    }
    
    if(dsty == corner_right) {
      dstl_right = dstwf;
      seg_right = 1;
    }
    
    /* Set the destination pointer */
    dp = dlp + fixtoi(dstl_left) * bpp;
    
    /* Set up source pointer and values */
    if(!seg_left) {
      switch(q) {
      case 0:
	xf = src_left;
	yf = 0;
	break;
      case 1:
	xf = srcwf;
	yf = src_left;
	break;
      case 2:
	xf = src_left;
	yf = srchf;
	break;
      case 3:
	xf = 0;
	yf = src_left;
	break;
      }
    } else {
      switch(q) {
      case 0:
	xf = 0;
	yf = src_left;
	break;
      case 1:
	xf = src_left;
	yf = 0;
	break;
      case 2:
	xf = srcwf;
	yf = src_left;
	break;
      case 3:
	xf = src_left;
	yf = srchf;
	break;
      }
    }
    
    /* Determine the line length */
    dstx = fixtoi(dstl_right - dstl_left) + 1;
    
    if(src_step_x) {
      if(src_step_x > 0)
	maxx = fixdiv((srcwf - xf), src_step_x) >> 16;
      else
	maxx = fixdiv(xf, -src_step_x) >> 16;
      
      if(maxx < dstx) dstx = maxx;
    }
    
    if(src_step_y) {
      if(src_step_y > 0)
	maxx = fixdiv((srchf - yf), src_step_y) >> 16;
      else
	maxx = fixdiv(yf, -src_step_y) >> 16;
      
      if(maxx < dstx) dstx = maxx;
    }
    
    xi = fixtoi(xf);  yi = fixtoi(yf);
    
    sp = (char *)s->pixels + xi * psx + yi * psy;
    
    /* Find the boundary bytes for the destination */
    dfp = dp + dstx * bpp;
    
    /* Copy from the source to the destination */
    while(dp < dfp) {
      switch(bpp) {
      case 1:
	*(Uint8 *)dp = *(Uint8 *)sp;
	break;
      case 2:
	*(Uint16 *)dp = *(Uint16 *)sp;
	break;
      case 4:
	*(Uint32 *)dp = *(Uint32 *)sp;
	break;
      }
      
      xf += src_step_x;  yf += src_step_y;
      xnew = fixtoi(xf);  ynew = fixtoi(yf);
      dx = xnew - xi;  dy = ynew - yi;
      sp += (dx * psx) + (dy * psy);
      xi = xnew;  yi = ynew;
      dp += bpp;
    }
    
    /* The following if block is identical to the following commented out
     * block.  This is wordier but more efficient. */
    if(q == 0 || q == 2) {
      if(seg_left) {
	dstl_left += dst_step_1;
	src_left += src_step_side;
      } else {
	dstl_left -= dst_step_0;
	src_left += src_step_top;
      }
    } else {
      if(seg_left) {
	dstl_left += dst_step_1;
	src_left += src_step_top;
      } else {
	dstl_left -= dst_step_0;
	src_left += src_step_side;
      }
    }
    
#if 0
    dstl_left += seg_left ? (dst_step_1) : (-dst_step_0);
    if(q == 0 || q == 2)
      src_left += seg_left ? src_step_side : src_step_top;
    else 
      src_left += seg_left ? src_step_top : src_step_side;
#endif
    dstl_right += seg_right ? (-dst_step_0) : (dst_step_1);
    
    /* Make sure we're staying on the surface */
    /* OPTIMIZEME */
#if 0
    if(dstl_left < 0) dstl_left = 0;
    if(dstl_right < 0) dstl_right = 0;
    if(dstl_left > dstwf) dstl_left = dstwf;
    if(dstl_right > dstwf) dstl_right = dstwf;
    if(src_left < 0) src_left = 0;
    if(src_left > srcwf) src_left = srcwf;
#endif
    
  }
  
  /* Finished; unlock surfaces */
  SDL_UnlockSurface(dst);
  SDL_UnlockSurface(s);
  
  return dst;
}
